import ast
import copy
import traceback

from llvmlite import ir
from ply import yacc

from common.spel_lexer import *
from common.utils import float_eq, log_and_raise
from log import LOGGER, write_both_ir_log
from spel4llvm.consts import (
    FUNC_FLOAT2STR,
    FUNC_INT2STR,
    FUNC_STR_GE,
    FUNC_STR_GT,
    FUNC_STR_LE,
    FUNC_STR_LT,
    FUNC_STR_NE,
    FUNC_STR_EQ,
    FUNC_STRLEN,
    FUNC_STR_CONCAT,
    OP_CALL,
    OP_INT2FLOAT,
    BUILTIN_POW,
    THIS_VAR,
    CTX_MODE_LOAD,
    SUBSCRIPT_CMD,
    CTX_MODE_STORE,
)
from spel4llvm.err_defs import ParseError
from spel4llvm.grammar_node import (
    GrammarNodeConstant,
    GrammarNodeTempVar,
    GrammarNode,
    GrammarNode_False,
    GrammarNode_True,
    GrammarNodeGlobalVar,
    get_llvm_constant_value,
    can_initialize,
    GrammarNode_Null,
)
from spel4llvm.grammar_value_types import (
    TP_INT,
    TP_FLOAT,
    TP_STR,
    decide_type,
    TP_BOOL,
    TP_CHAR,
    decide_arith_type,
    GrammarValueType,
    infer_type_from_py_value,
    TP_NULL,
    type_from_str,
    compare_type_ignore_elem_type,
)
from spel4llvm.llvm_runner import (
    LLVMRunner,
    prepare_basic_block,
)
from spel4llvm.llvm_types import (
    LLVM_INT_TYPE,
    LLVM_FLOAT_TYPE,
    LLVM_STR_TYPE,
    LLVM_BOOL_TYPE,
)
from spel4llvm.op_mappings import UNARY_OP_MAPPING, BIN_OP_MAPPING, CMP_OP_MAPPING
from spel4llvm.value_transfer import (
    py_value_to_llvm_global,
    str_value_to_llvm_globalvar,
)


def p_stmt(p):
    """
    stmt : expr
        | assign_stmt
        | empty
    """
    ir_generator = IRGen(p.parser.context)
    p[0] = ir_generator.gen_ir(p[1])


def p_assign_stmt(p):
    """
    assign_stmt : expr ASSIGN expr
    """
    p[0] = p.parser.pseudo_ir_gen.build_assignment_pseudo_ir(p[1], p[3])


def p_expr(p):
    """
    expr : arith_expr
        | unary_expr
        | rel_expr
        | tri_expr
        | elvis_expr
        | list_def
        | dict_def
        | indexer
        | LPAR expr RPAR
        | var_def
        | property_def
        | bean_def
        | attr_def
        | meth_call
        | func_call
        | literal_def
        | project_expr
        | select_expr
        | type_ref_def
    """
    if len(p) == 2:
        p[0] = p[1]
    else:
        p[0] = p[2]


def p_select_expr(p):
    """
    select_expr : expr member_ref SELECT rel_expr RSQUARE
    """
    p[0] = ast.ListComp(
        elt=ast.Name(id=THIS_VAR, ctx=ast.Load()),
        generators=[
            ast.comprehension(
                target=ast.Name(id=THIS_VAR, ctx=ast.Store()),
                iter=py_adapter.build_call_func_node(
                    py_adapter.BuiltinFunctions.get_iterable.__name__, [p[1]]
                ),
                ifs=[p[4]],
                is_async=0,
            )
        ],
    )


def p_project_expr(p):
    """
    project_expr : expr member_ref PROJECT expr RSQUARE
    """
    p[0] = ast.ListComp(
        elt=p[4],
        generators=[
            ast.comprehension(
                target=ast.Name(id=THIS_VAR, ctx=ast.Store()),
                iter=py_adapter.build_call_func_node(
                    py_adapter.BuiltinFunctions.get_iterable.__name__, [p[1]]
                ),
                ifs=[],
                is_async=0,
            )
        ],
    )


def p_bean_def(p):
    """
    bean_def : BEAN_REF ID
    """


def p_elvis_expr(p):
    """
    elvis_expr : expr ELVIS expr
    """
    p[0] = p[1] if p[1].value_type != TP_NULL else p[3]


def p_tri_expr(p):
    """
    tri_expr : expr QMARK expr COLON expr
    """
    p[0] = p.parser.pseudo_ir_gen.build_tri_pseudo_ir(p[1], p[3], p[5])


def p_rel_expr_compare(p):
    """
    rel_expr : expr EQ expr
            | expr NE expr
            | expr LE expr
            | expr LT expr
            | expr GE expr
            | expr GT expr

    """
    p[0] = p.parser.pseudo_ir_gen.build_cmp_pseudo_ir(p[2], p[1], p[3])


BOOL_OP_MAPPING = {"&&": "and_", "and": "and_", "||": "or_", "or": "or_"}


def p_rel_expr_bool(p):
    """
    rel_expr : expr AND expr
            | expr OR expr
    """
    p[0] = p.parser.pseudo_ir_gen.build_bool_pseudo_ir(p[2], p[1], p[3])


def p_rel_expr_unary(p):
    "rel_expr : NOT expr"
    p[0] = p.parser.pseudo_ir_gen.build_cmp_pseudo_ir("==", p[2], GrammarNode_False)


def p_rel_expr_match(p):
    """
    rel_expr : expr REG_MATCH expr
    """
    p[0] = ast.Call(
        func=ast.Name(id="match_whole", ctx=ast.Load()), args=[p[3], p[1]], keywords=[]
    )


def p_rel_expr_instof(p):
    """
    rel_expr : expr INST_OF type_ref_def
    """

    p[0] = p.parser.pseudo_ir_gen.build_bool_pseudo_ir(
        "and",
        GrammarNodeConstant(
            compare_type_ignore_elem_type(p[1].value_type, p[3]), TP_BOOL
        ),
        GrammarNodeConstant(True, TP_BOOL),
    )


def p_rel_expr_between(p):
    """
    rel_expr : expr BETWEEN LCURLY expr COMMA expr RCURLY
    """
    p[0] = p.parser.pseudo_ir_gen.build_bool_pseudo_ir(
        "and",
        p.parser.pseudo_ir_gen.build_cmp_pseudo_ir(">=", p[1], p[4]),
        p.parser.pseudo_ir_gen.build_cmp_pseudo_ir("<=", p[1], p[6]),
    )


def p_arith_expr(p):
    """
    arith_expr : expr PLUS expr
            | expr MINUS expr
            | expr MULT expr
            | expr DIV expr
            | expr MOD expr
            | expr POWER expr
            | expr INC
            | expr DEC
    """
    if len(p) == 4:
        lhs_node = p[1]
        rhs_node = p[3]
        op_code = p[2]
        if op_code == "^":
            p[0] = p.parser.pseudo_ir_gen.build_call_pseudo_ir(
                BUILTIN_POW, (lhs_node, rhs_node), TP_FLOAT, (TP_FLOAT, TP_FLOAT)
            )
        else:
            p[0] = p.parser.pseudo_ir_gen.build_arith_pseudo_ir(
                op_code, lhs_node, rhs_node
            )
    else:
        pass


def p_unary_expr_inc_dec(p):
    """
    unary_expr : INC expr %prec UINC
                | DEC expr %prec UDEC
    """
    p[0] = ast.BinOp(left=p[2], op=UNARY_OP_MAPPING[p[1]], right=ast.Constant(1))
    p.parser.context.add_inc_dec_node(p[2], p[1])


def p_unary_expr_uminus(p):
    """
    unary_expr : MINUS expr %prec UMINUS
    """
    p[0] = p.parser.pseudo_ir_gen.build_arith_pseudo_ir(p[1], p[2], None)


def p_literal_def_null(p):
    """
    literal_def : NULL
    """
    p[0] = GrammarNode_Null


def p_literal_def_true(p):
    """
    literal_def : TRUE
    """
    p[0] = GrammarNode_True


def p_literal_def_false(p):
    """
    literal_def : FALSE
    """
    p[0] = GrammarNode_False


def p_literal_def_str(p):
    """
    literal_def : STR
    """
    p[0] = p.parser.pseudo_ir_gen.build_literal_str_define_pseudo_ir(p[1])


def p_literal_def_int(p):
    """
    literal_def : INTEGER
    """
    p[0] = GrammarNodeConstant(p[1], TP_INT)


def p_literal_def_float(p):
    """
    literal_def : FLOATING
    """
    p[0] = GrammarNodeConstant(p[1], TP_FLOAT)


def p_indexer(p):
    """
    indexer : dict_def  LSQUARE expr RSQUARE
            | var_def LSQUARE expr RSQUARE
            | attr_def LSQUARE expr RSQUARE
            | indexer LSQUARE expr RSQUARE
    """
    p[0] = p.parser.pseudo_ir_gen.build_subscript_pseudo_ir(p[1], p[3])


def p_indexer_list(p):
    """
    indexer : list_def LSQUARE expr RSQUARE
    """
    p[0] = p.parser.pseudo_ir_gen.build_list_subscript_pseudo_ir(p[1], p[3])


def p_indexer_str(p):
    "indexer : STR LSQUARE expr RSQUARE"
    p[0] = p.parser.pseudo_ir_gen.build_literal_str_subscript_pseudo_ir(p[1], p[3])


def p_dict_def(p):
    """
    dict_def : LCURLY kv_list RCURLY
    """
    p[0] = ast.Dict(keys=list(p[2].keys()), values=list(p[2].values()))


def p_kv_list(p):
    """
    kv_list : expr COLON expr
           | kv_list COMMA expr COLON expr
    """
    if len(p) == 4:
        p[0] = {p[1]: p[3]}
    else:
        p[1][p[3]] = p[5]
        p[0] = p[1]


def p_list_def(p):
    """
    list_def : LCURLY expr_list RCURLY
    """
    p[0] = p.parser.pseudo_ir_gen.build_list_define_pseudo_ir(p[2])


def p_func_call(p):
    """
    func_call : HASH ID args
    """
    func_name = p[2]
    sig = p.parser.pseudo_ir_gen.get_func_signature(func_name)
    p[0] = p.parser.pseudo_ir_gen.build_call_pseudo_ir(func_name, p[3], sig[0], sig[1])


def p_meth_call(p):
    """
    meth_call : expr member_ref ID args
    """
    owner_node = p[1]
    if owner_node.value_type.is_list_type() and p[3] == "size":
        p[0] = p.parser.pseudo_ir_gen.build_list_size_pseudo_ir(owner_node)
    else:
        func_name = str(owner_node.value_type) + "_" + p[3]
        sig = p.parser.pseudo_ir_gen.get_func_signature(func_name)
        p[0] = p.parser.pseudo_ir_gen.build_call_pseudo_ir(
            func_name, [owner_node, *p[4]], sig[0], sig[1]
        )


def p_type_ref_def(p):
    """
    type_ref_def : TYPE_REF LPAR cls_def RPAR
    """
    p[0] = type_from_str(".".join(p[3]))


def p_cls_def(p):
    """
    cls_def : ID
            | cls_def DOT ID
    """
    if len(p) == 2:
        p[0] = [p[1]]
    else:
        p[1].append(p[3])
        p[0] = p[1]


def p_args(p):
    "args : LPAR expr_list RPAR"
    p[0] = p[2]


def p_expr_list_many(p):
    """
    expr_list : expr_list COMMA expr
    """
    p[1].append(p[3])
    p[0] = p[1]


def p_expr_list_single(p):
    """
    expr_list : expr
    """
    p[0] = [p[1]]


def p_expr_list_empty(p):
    """
    expr_list : empty
    """
    p[0] = []


def p_property_def(p):
    "property_def : ID"
    p[0] = ast.Attribute(
        value=ast.Name(id=THIS_VAR, ctx=ast.Load()), attr=p[1], ctx=ast.Load()
    )


def p_var_def(p):
    """
    var_def : HASH ID
    """
    p[0] = p.parser.pseudo_ir_gen.build_var_decl_pseudo_ir(p[2])


def p_attr_def(p):
    """
    attr_def : expr member_ref ID
    """
    p.parser.context.add_member(p[3])
    attr_node = ast.Attribute(value=p[1], attr=p[3], ctx=ast.Load())
    if p[2] == ".":
        p[0] = attr_node
    else:
        p[0] = ast.IfExp(
            test=ast.Compare(
                left=p[1], ops=[ast.NotEq()], comparators=[ast.Constant(None)]
            ),
            body=attr_node,
            orelse=ast.Constant(None),
        )


def p_member_ref(p):
    """
    member_ref : DOT
            | SAFE_NAVI
    """
    p[0] = p[1]


def p_empty(p):
    "empty :"
    pass


def p_error(p):
    err_msg = "Syntax error at EOF"
    if p:
        err_msg = "Syntax error at '%s', line:%d, pos:%d" % (
            p.value,
            p.lineno,
            p.lexpos,
        )
    raise ParseError(err_msg)


class SymInfo:
    def __init__(self, sym_name, sym_type, sym_value):
        self.sym_name = sym_name
        self.sym_type = sym_type
        self.sym_value = sym_value

    def __repr__(self):
        return "SymInfo{name=%s, type=%s, value=%s}" % (
            self.sym_name,
            self.sym_type,
            self.sym_value,
        )


# context during pseudo-ir and llvm-ir generation
class ParseContext:
    def __init__(self):
        self.errors = []
        self.inc_dec_nodes = []
        self.tacs = []
        self.tmp_var_seq = 0
        self.global_var_seq = 0
        self.const_str_pool = set()
        self.sym_table = {}

    def collect_const_str(self, s: str):
        self.const_str_pool.add(s)

    def init_sym_table(self, run_ctx):
        self.sym_table.clear()
        if run_ctx:
            for k in run_ctx:
                self.sym_table[k] = SymInfo(
                    k, infer_type_from_py_value(run_ctx[k]), run_ctx[k]
                )

    def check_syms(self):
        undefined_syms = []
        for sym_name in self.sym_table:
            sym_info = self.sym_table[sym_name]
            if sym_info.sym_type is None:
                undefined_syms.append(sym_name)

        LOGGER.info("check_syms sym-table:%s" % self.sym_table)
        if undefined_syms:
            log_and_raise(
                ParseError, "symbols:%s undefined!" % (",".join(undefined_syms))
            )

    def collect_sym(self, var_name, grammar_typ: GrammarValueType):
        if var_name in self.sym_table:
            # grammar_typ=None means var declaration, we cannot override existing type
            if grammar_typ is not None:
                LOGGER.info(
                    "symbol:%s type overrided: %s=>%s"
                    % (var_name, self.sym_table[var_name].sym_type, grammar_typ)
                )
                self.sym_table[var_name].sym_type = grammar_typ
                self.sym_table[var_name].sym_value = None
        else:
            self.sym_table[var_name] = SymInfo(var_name, grammar_typ, None)

    def get_sym(self, var_name):
        return self.sym_table.get(var_name)

    def add_tac(self, tac_code):
        self.tacs.append(list(tac_code))

    def find_tac_by_result_id(self, result_id):
        for i in range(len(self.tacs) - 1, -1, -1):
            cur_tac = self.tacs[i]
            if cur_tac[len(cur_tac) - 1] == result_id:
                return cur_tac
        return None

    def gen_tmp_var(self):
        n = "__t%d" % self.tmp_var_seq
        self.tmp_var_seq += 1
        return n

    def gen_global_var(self):
        n = "__g%d" % self.global_var_seq
        self.global_var_seq += 1
        return n

    def add_inc_dec_node(self, node, op):
        new_node = copy.copy(node)
        reset_assign_ctx(new_node)
        self.inc_dec_nodes.append((new_node, op))

    def complement_inc_dec_cmd(self):
        return [
            ast.AugAssign(target=node, op=UNARY_OP_MAPPING[op], value=ast.Constant(1))
            for node, op in self.inc_dec_nodes
        ]

    def collect_error(self, msg):
        self.errors.append(msg)

    def has_error(self):
        return len(self.errors) != 0

    def get_errors(self):
        return ";".join(self.errors)

    def clear(self):
        self.errors.clear()
        self.inc_dec_nodes.clear()
        self.tacs.clear()
        self.tmp_var_seq = 0
        self.global_var_seq = 0
        self.const_str_pool.clear()
        self.sym_table.clear()


class PseudoIRGen:
    TYPE_CVT_MAPPING = {
        (TP_INT, TP_FLOAT): OP_INT2FLOAT,
        (TP_INT, TP_STR): "call#int2str",
        (TP_FLOAT, TP_STR): "call#float2str",
    }

    def __init__(self, ctx: ParseContext):
        self.ctx = ctx
        self.builtin_funcs_info = {}
        self.prepare_builtins()

    def prepare_builtins(self):
        self.builtin_funcs_info[FUNC_STRLEN] = (TP_INT, (TP_STR,))
        self.builtin_funcs_info["str_length"] = self.builtin_funcs_info[FUNC_STRLEN]
        # self.builtin_funcs_info['list_size'] = (TP_INT, (TP_LIST,))

    def add_tac(self, tac_code):
        self.ctx.add_tac(tac_code)

    def gen_tmp_var(self):
        return self.ctx.gen_tmp_var()

    def gen_global_var(self):
        return self.ctx.gen_global_var()

    def build_var_decl_pseudo_ir(self, var_name):
        sym_info = self.ctx.get_sym(var_name)
        if sym_info is None:
            # when var-declaration and no pre-defined var, we dont know var type
            typ = None
            self.ctx.collect_sym(var_name, typ)
        else:
            typ = sym_info.sym_type
        return GrammarNodeGlobalVar(var_name, typ, user_visible=True)

    def build_subscript_pseudo_ir(self, owner_node: GrammarNode, idx: GrammarNode):
        if owner_node.value_type.is_list_type():
            return self.build_list_subscript_pseudo_ir(owner_node, idx)
        elif owner_node.value_type == TP_STR:
            return self.build_str_subscript_pseudo_ir(owner_node, idx)
        else:
            log_and_raise(ParseError, "node:%s not support subscript" % owner_node)

    def build_list_size_pseudo_ir(self, list_node: GrammarNode):
        ptr_node = self._build_pseudo_ir_with_return(
            "gep", (list_node, [LLVM_INT_TYPE(0), LLVM_INT_TYPE(0)]), TP_INT
        )
        return self.build_load_cmd(ptr_node)

    def build_load_cmd(self, ptr_node):
        return self._build_pseudo_ir_with_return(
            "load", (ptr_node,), ptr_node.value_type
        )

    def build_list_subscript_pseudo_ir(self, list_node: GrammarNode, idx: GrammarNode):
        return self._build_pseudo_ir_with_return(
            SUBSCRIPT_CMD,
            (list_node, idx, CTX_MODE_LOAD),
            list_node.value_type.elem_type,
        )

    def build_literal_str_subscript_pseudo_ir(self, raw_str: str, idx: GrammarNode):
        str_node = self.build_literal_str_define_pseudo_ir(raw_str)
        return self.build_str_subscript_pseudo_ir(str_node, idx)

    def build_str_subscript_pseudo_ir(self, str_node: GrammarNode, idx: GrammarNode):
        return self._build_pseudo_ir_with_return(
            SUBSCRIPT_CMD, (str_node, idx, CTX_MODE_LOAD), TP_CHAR
        )

    def check_list_elem_types(self, grammar_nodes: list):
        if not grammar_nodes:
            return TP_INT

        std_typ = grammar_nodes[0].value_type
        errs = []
        for i in range(1, len(grammar_nodes)):
            if grammar_nodes[i].value_type != std_typ:
                errs.append("list element #%d is not of type %s" % (i, std_typ))

        if errs:
            log_and_raise(TypeError, ";".join(errs))

        return std_typ

    def build_list_define_pseudo_ir(self, grammar_nodes: list):
        elem_type = self.check_list_elem_types(grammar_nodes)
        id_name = self.gen_global_var()
        self.add_tac(("#mklist", id_name, elem_type, grammar_nodes, None))
        return GrammarNodeGlobalVar(id_name, GrammarValueType.make_list_type(elem_type))

    def build_literal_str_define_pseudo_ir(self, s: str):
        self.ctx.collect_const_str(s)
        return GrammarNodeConstant(s, TP_STR)

    def build_tri_pseudo_ir(
        self, cond: GrammarNode, arg1: GrammarNode, arg2: GrammarNode
    ):
        if cond.value_type != TP_BOOL:
            log_and_raise(
                TypeError,
                "cannot convert %s type to boolean when tri operation"
                % (cond.value_type),
            )

        if arg1.value_type != arg2.value_type:
            log_and_raise(TypeError, "arg1/arg2 type should be same when tri operation")

        return self._build_pseudo_ir_with_return(
            "select", (cond, arg1, arg2), arg1.value_type
        )

    def build_bool_pseudo_ir(self, op_code: str, arg1: GrammarNode, arg2: GrammarNode):
        op_code = op_code.lower()
        if arg1.value_type != TP_BOOL:
            raise TypeError(
                "cannot convert %s type to boolean when and/or operation"
                % (arg1.value_type)
            )
        if arg2.value_type != TP_BOOL:
            raise TypeError(
                "cannot convert %s type to boolean when and/or operation"
                % (arg2.value_type)
            )

        return self._build_pseudo_ir_with_return(
            BOOL_OP_MAPPING[op_code], (arg1, arg2), TP_BOOL
        )

    def get_func_signature(self, func_name):
        sig = self.builtin_funcs_info.get(func_name)
        if sig is None:
            log_and_raise(ParseError, "function:%s signature NOT found!" % func_name)
        return sig

    def build_assignment_pseudo_ir(self, lhs_node: GrammarNode, rhs_node: GrammarNode):
        if isinstance(lhs_node, GrammarNodeGlobalVar):
            lhs_node.set_value_type(rhs_node.value_type)

            # update spel var symbol's type
            self.ctx.collect_sym(lhs_node.id, lhs_node.value_type)
        elif isinstance(lhs_node, GrammarNodeTempVar):
            if lhs_node.value_type != rhs_node.value_type:
                log_and_raise(
                    TypeError,
                    "cannot assign %s to %s"
                    % (rhs_node.value_type, lhs_node.value_type),
                )

            tac = self.ctx.find_tac_by_result_id(lhs_node.id)
            if tac is not None:
                tac[-2] = CTX_MODE_STORE
            else:
                log_and_raise(
                    ParseError, "cannot find pseudo ir of result-id:%s" % lhs_node.id
                )
        else:
            log_and_raise(ParseError, "%s is not assignable" % lhs_node)

        self.add_tac(("#assign", lhs_node, rhs_node, None))
        return lhs_node

    def build_call_pseudo_ir(self, func_name, arg_nodes, ret_type, arg_types):
        id_name = self.gen_tmp_var()
        new_args = [
            self.process_type_conversion(arg_node, arg_type)
            for arg_node, arg_type in zip(arg_nodes, arg_types)
        ]
        self.add_tac((OP_CALL + "#" + func_name, *new_args, id_name))
        return GrammarNodeTempVar(id_name, ret_type)

    def build_cmp_pseudo_ir(self, op_code: str, arg1: GrammarNode, arg2: GrammarNode):
        op_code = op_code.lower()
        final_type = self.calc_final_type(arg1, arg2)
        new_arg1 = self.process_type_conversion(arg1, final_type)
        new_arg2 = self.process_type_conversion(arg2, final_type)
        instr_code = CMP_OP_MAPPING.get((op_code, final_type))
        if instr_code is None:
            log_and_raise(
                TypeError,
                "cannot support %s operation between type:%s" % (op_code, final_type),
            )
        return self._build_pseudo_ir_with_return(
            instr_code, (new_arg1, new_arg2), TP_BOOL
        )

    def build_arith_pseudo_ir(self, op_code: str, arg1: GrammarNode, arg2: GrammarNode):
        if arg2 is None:
            # unary op
            return self._build_pseudo_ir_with_return(
                UNARY_OP_MAPPING[(op_code.lower(), arg1.value_type)],
                (arg1, None),
                arg1.value_type,
            )
        else:
            final_type = self.calc_arith_final_type(arg1, arg2)
            instr_code = BIN_OP_MAPPING.get((op_code.lower(), final_type))
            if instr_code is None:
                log_and_raise(
                    TypeError,
                    "%s type cannot support op:%s" % (final_type, op_code),
                )
            new_arg1 = self.process_type_conversion(arg1, final_type)
            new_arg2 = self.process_type_conversion(arg2, final_type)
            return self._build_pseudo_ir_with_return(
                instr_code,
                (new_arg1, new_arg2),
                final_type,
            )

    def _build_pseudo_ir_with_return(self, instr_code, args, final_type):
        id_name = self.gen_tmp_var()
        self.add_tac((instr_code, *args, id_name))
        return GrammarNodeTempVar(id_name, final_type)

    @staticmethod
    def calc_final_type(arg1: GrammarNode, arg2: GrammarNode):
        if arg2 is None:
            return arg1.value_type
        else:
            return decide_type(arg1.value_type, arg2.value_type)

    @staticmethod
    def calc_arith_final_type(arg1: GrammarNode, arg2: GrammarNode):
        if arg2 is None:
            return arg1.value_type
        else:
            return decide_arith_type(arg1.value_type, arg2.value_type)

    def process_type_conversion(self, node: GrammarNode, final_type):
        if final_type != node.value_type:
            cvt_tmp_var = self.gen_tmp_var()
            instr_code = self.TYPE_CVT_MAPPING.get((node.value_type, final_type))
            if instr_code is None:
                log_and_raise(
                    TypeError,
                    "cannot perform conversion from %s to %s"
                    % (node.value_type, final_type),
                )
            if instr_code.startswith(OP_CALL):
                self.add_tac((instr_code, node, cvt_tmp_var))
            else:
                self.add_tac(
                    (
                        instr_code,
                        node,
                        final_type.to_llvm_type(),
                        cvt_tmp_var,
                    )
                )
            return GrammarNodeTempVar(cvt_tmp_var, final_type)
        else:
            return node


# context during llvm ir generation
class IRGenContext:
    def __init__(self):
        self.tmp_var_mapping = {}
        self.const_str_mapping = {}
        self.global_var_mapping = {}

    def collect_tmp_var(self, tmp_id, tmp_var):
        self.tmp_var_mapping[tmp_id] = tmp_var

    def get_tmp_var(self, tmp_id):
        return self.tmp_var_mapping.get(tmp_id)

    def collect_const_str(self, str_v: str, global_value: ir.GlobalVariable):
        self.const_str_mapping[str_v] = global_value

    def get_const_str(self, str_v: str):
        return self.const_str_mapping.get(str_v)

    def collect_global_var(self, global_var_name: str, global_value: ir.GlobalVariable):
        self.global_var_mapping[global_var_name] = global_value

    def get_global_var(self, global_var_name: str):
        return self.global_var_mapping.get(global_var_name)


class IRGen:
    def __init__(self, parse_ctx: ParseContext):
        self.parse_ctx = parse_ctx
        self.builtin_funcs = {}

    def alloc_sym_storage(
        self, mod: ir.Module, builder: ir.IRBuilder, gen_ctx: IRGenContext
    ):
        LOGGER.info("alloc_sym_storage sym-tables:%s" % self.parse_ctx.sym_table)
        for sym_name in self.parse_ctx.sym_table:
            sym_info = self.parse_ctx.sym_table[sym_name]
            # symbol value is assigned in SPEL, NOT transferred from outside
            if sym_info.sym_value is None:
                continue

            gvar = py_value_to_llvm_global(
                mod, sym_info.sym_value, sym_info.sym_type, sym_name, builder, gen_ctx
            )
            gen_ctx.collect_global_var(sym_name, gvar)

    def prepare_builtins(self, mod: ir.Module):
        self.builtin_funcs[BUILTIN_POW] = mod.declare_intrinsic(
            "llvm.pow", (LLVM_FLOAT_TYPE,)
        )

        self.builtin_funcs[FUNC_STRLEN] = ir.Function(
            mod, ir.FunctionType(LLVM_INT_TYPE, (LLVM_STR_TYPE,)), name=FUNC_STRLEN
        )
        self.builtin_funcs["str_length"] = self.builtin_funcs[FUNC_STRLEN]
        self.builtin_funcs[FUNC_STR_CONCAT] = ir.Function(
            mod,
            ir.FunctionType(LLVM_STR_TYPE, (LLVM_STR_TYPE, LLVM_STR_TYPE)),
            name=FUNC_STR_CONCAT,
        )
        self.builtin_funcs[FUNC_STR_EQ] = ir.Function(
            mod,
            ir.FunctionType(LLVM_BOOL_TYPE, (LLVM_STR_TYPE, LLVM_STR_TYPE)),
            name=FUNC_STR_EQ,
        )
        self.builtin_funcs[FUNC_STR_NE] = ir.Function(
            mod,
            ir.FunctionType(LLVM_BOOL_TYPE, (LLVM_STR_TYPE, LLVM_STR_TYPE)),
            name=FUNC_STR_NE,
        )
        self.builtin_funcs[FUNC_STR_LT] = ir.Function(
            mod,
            ir.FunctionType(LLVM_BOOL_TYPE, (LLVM_STR_TYPE, LLVM_STR_TYPE)),
            name=FUNC_STR_LT,
        )
        self.builtin_funcs[FUNC_STR_LE] = ir.Function(
            mod,
            ir.FunctionType(LLVM_BOOL_TYPE, (LLVM_STR_TYPE, LLVM_STR_TYPE)),
            name=FUNC_STR_LE,
        )
        self.builtin_funcs[FUNC_STR_GT] = ir.Function(
            mod,
            ir.FunctionType(LLVM_BOOL_TYPE, (LLVM_STR_TYPE, LLVM_STR_TYPE)),
            name=FUNC_STR_GT,
        )
        self.builtin_funcs[FUNC_STR_GE] = ir.Function(
            mod,
            ir.FunctionType(LLVM_BOOL_TYPE, (LLVM_STR_TYPE, LLVM_STR_TYPE)),
            name=FUNC_STR_GE,
        )
        self.builtin_funcs[FUNC_INT2STR] = ir.Function(
            mod,
            ir.FunctionType(LLVM_STR_TYPE, (LLVM_INT_TYPE,)),
            name=FUNC_INT2STR,
        )
        self.builtin_funcs[FUNC_FLOAT2STR] = ir.Function(
            mod,
            ir.FunctionType(LLVM_STR_TYPE, (LLVM_FLOAT_TYPE,)),
            name=FUNC_FLOAT2STR,
        )

    def gen_ir(self, top_node):
        if not top_node:
            LOGGER.error("gen pseudo ir failed!")
            return None

        # first of all, we check symbol validation
        self.parse_ctx.check_syms()

        LOGGER.info("topnode:%s" % top_node)
        mod, block = prepare_basic_block(
            ir.FunctionType(top_node.value_type.to_llvm_return_type(), ())
        )

        self.prepare_builtins(mod)
        gen_ctx = IRGenContext()

        for i, s in enumerate(self.parse_ctx.const_str_pool):
            gen_ctx.collect_const_str(
                s, str_value_to_llvm_globalvar(mod, s, ".str%d" % i, user_visible=False)
            )

        builder = ir.IRBuilder(block)
        self.alloc_sym_storage(mod, builder, gen_ctx)

        result = self.process_pseudo_tacs(builder, gen_ctx, mod)

        if result is None:
            result = top_node.to_llvm(builder, gen_ctx)

        builder.ret(result)
        return mod

    def process_pseudo_tacs(self, builder, gen_ctx, mod):
        result = None
        self.print_tacs()
        LOGGER.info("tacs processed:")
        for tac in self.parse_ctx.tacs:
            raw_instr_code = tac[0]
            result_id = tac[-1]
            if raw_instr_code.startswith("#"):
                # our custom instr don't need build_operands to cvt to llvm
                result = getattr(self, raw_instr_code[1:])(
                    tac[1:], mod, builder, gen_ctx
                )
            else:
                if result_id is not None:
                    result = self.get_builder_op(builder, raw_instr_code)(
                        *(self.build_operands(tac, builder, gen_ctx)),
                        name=result_id,
                        **self.get_instruction_extra_params(raw_instr_code)
                    )
                else:
                    self.get_builder_op(builder, raw_instr_code)(
                        *(self.build_operands(tac, builder, gen_ctx)),
                        **self.get_instruction_extra_params(raw_instr_code)
                    )
            # llvm and custom instr both may return temp var
            if result_id is not None:
                gen_ctx.collect_tmp_var(result_id, result)
            LOGGER.info(tac)

        return result

    def print_tacs(self):
        LOGGER.info("tacs to process:")
        for tac in self.parse_ctx.tacs:
            LOGGER.info(tac)

    def list_can_initialize(self, elem_grammar_nodes):
        for e in elem_grammar_nodes:
            if not can_initialize(e):
                return False
        return True

    def build_list_storage(
        self,
        module,
        builder: ir.IRBuilder,
        elem_llvm_type,
        elem_grammar_nodes: list,
        gen_ctx: IRGenContext,
        storage_name,
    ):
        can_init = self.list_can_initialize(elem_grammar_nodes)
        arr_sz = len(elem_grammar_nodes)
        arr_type = ir.ArrayType(elem_llvm_type, arr_sz)
        arr_var = ir.GlobalVariable(module, arr_type, name=storage_name)
        arr_var.global_constant = False
        arr_var.unnamed_addr = True
        arr_var.align = None
        arr_var.linkage = "private"

        if can_init:
            arr_var.initializer = ir.Constant(
                arr_type, [x.to_llvm(builder, gen_ctx) for x in elem_grammar_nodes]
            )
        else:
            for i, elem_node in enumerate(elem_grammar_nodes):
                ptr = builder.gep(
                    arr_var,
                    [LLVM_INT_TYPE(0), LLVM_INT_TYPE(i)],
                )
                builder.store(elem_node.load(builder, gen_ctx), ptr)

        return arr_var

    def build_list_globalvar(
        self,
        module,
        builder: ir.IRBuilder,
        elem_llvm_type,
        elem_grammar_nodes,
        gen_ctx: IRGenContext,
        list_name,
    ):
        # storage part
        storage_var = self.build_list_storage(
            module,
            builder,
            elem_llvm_type,
            elem_grammar_nodes,
            gen_ctx,
            list_name + ".storage",
        )

        # ref part
        list_type = ir.LiteralStructType([LLVM_INT_TYPE, elem_llvm_type.as_pointer()])
        list_var = ir.GlobalVariable(module, list_type, name=list_name)
        list_var.global_constant = True
        list_var.unnamed_addr = True
        list_var.align = None
        list_var.linkage = "private"
        list_var.initializer = ir.Constant(
            list_type,
            [
                LLVM_INT_TYPE(len(elem_grammar_nodes)),
                storage_var.gep([LLVM_INT_TYPE(0), LLVM_INT_TYPE(0)]),
            ],
        )
        return list_var

    def mklist(
        self, param_nodes, mod: ir.Module, builder: ir.IRBuilder, gen_ctx: IRGenContext
    ):
        list_name = param_nodes[0]
        elem_llvm_type = param_nodes[1].to_llvm_type()
        raw_elems = param_nodes[2]
        list_var = self.build_list_globalvar(
            mod, builder, elem_llvm_type, raw_elems, gen_ctx, list_name
        )

        gen_ctx.collect_global_var(list_name, list_var)
        return list_var

    def build_spelvar_globalvar(
        self,
        mod: ir.Module,
        value_node: GrammarNode,
        global_var_name: str,
        builder: ir.IRBuilder,
        gen_ctx: IRGenContext,
    ):
        llvm_typ = value_node.value_type.to_llvm_type()
        gvar = ir.GlobalVariable(mod, llvm_typ, name=global_var_name)
        gvar.global_constant = False
        gvar.unnamed_addr = True  # addr is insignificant
        gvar.align = 1  # const str align=1
        gvar.linkage = "internal"  # for outer access, private is not allowed

        if can_initialize(value_node):
            gvar.initializer = ir.Constant(
                llvm_typ, get_llvm_constant_value(value_node, builder, gen_ctx)
            )
        else:
            ptr = builder.gep(
                gvar,
                [LLVM_INT_TYPE(0)],
            )
            builder.store(value_node.load(builder, gen_ctx), ptr)

        return gvar

    def assign(
        self, param_nodes, mod: ir.Module, builder: ir.IRBuilder, gen_ctx: IRGenContext
    ):
        lhs_node = param_nodes[0]
        rhs_node = param_nodes[1]
        if isinstance(lhs_node, GrammarNodeGlobalVar):
            spelvar = self.build_spelvar_globalvar(
                mod, rhs_node, lhs_node.id, builder, gen_ctx
            )
            gen_ctx.collect_global_var(lhs_node.id, spelvar)

        elif isinstance(lhs_node, GrammarNodeTempVar):
            builder.store(
                rhs_node.load(builder, gen_ctx), lhs_node.to_llvm(builder, gen_ctx)
            )
        else:
            log_and_raise(ParseError, "%s is not assignable" % lhs_node)

        return rhs_node.to_llvm(builder, gen_ctx)

    def subscript(
        self, param_nodes, mod: ir.Module, builder: ir.IRBuilder, gen_ctx: IRGenContext
    ):
        owner_node = param_nodes[0]
        idx_node = param_nodes[1]
        ctx_mode = param_nodes[2]
        result_id_name = param_nodes[3]
        if owner_node.value_type == TP_STR:
            if ctx_mode == CTX_MODE_LOAD:
                elem_ptr = builder.gep(
                    owner_node.to_llvm(builder, gen_ctx),
                    [idx_node.to_llvm(builder, gen_ctx)],
                )
                # in ctx=load, we should call load instruction
                return builder.load(elem_ptr, name=result_id_name)
            else:
                return builder.gep(
                    owner_node.to_llvm(builder, gen_ctx),
                    [idx_node.to_llvm(builder, gen_ctx)],
                    name=result_id_name,
                )
        elif owner_node.value_type.is_list_type():
            # get elem-type**
            double_ptr = builder.gep(
                owner_node.to_llvm(builder, gen_ctx),
                [LLVM_INT_TYPE(0), LLVM_INT_TYPE(1)],
            )
            # get elem-type*
            ptr = builder.load(double_ptr)

            if ctx_mode == CTX_MODE_LOAD:
                if owner_node.value_type.elem_type.is_list_type():
                    # return elem-type* when elem-type==list
                    return builder.gep(
                        ptr, [idx_node.to_llvm(builder, gen_ctx)], name=result_id_name
                    )
                else:
                    elem_ptr = builder.gep(ptr, [idx_node.to_llvm(builder, gen_ctx)])
                    # return elem-type
                    return builder.load(elem_ptr, name=result_id_name)
            else:
                return builder.gep(
                    ptr, [idx_node.to_llvm(builder, gen_ctx)], name=result_id_name
                )

        else:
            log_and_raise(
                ParseError,
                "subscript NOT supported in type:%s" % (owner_node.value_type),
            )

    _INSTR_EXTRA_PARAMS = {"gep": {"inbounds": True}}

    def get_instruction_extra_params(self, instr_code):
        return self._INSTR_EXTRA_PARAMS.get(instr_code, {})

    def get_builder_op(self, builder, instr_code):
        operation = getattr(builder, instr_code, None)
        if operation is not None:
            return operation

        real_instr_code, first_param = instr_code.split("#")
        operation = getattr(builder, real_instr_code)

        if real_instr_code == OP_CALL:
            func_name = first_param
            first_param = self.builtin_funcs.get(func_name)
            if first_param is None:
                log_and_raise(ParseError, "builtin:%s not registered!" % func_name)

        def builder_op_wrapper(param1):
            def do_operation(*args, **kwargs):
                return operation(param1, *args, **kwargs)

            return do_operation

        return builder_op_wrapper(first_param)

    def build_operands(self, tac, builder, gen_ctx):
        operands = []
        if tac[0].startswith(OP_CALL):
            operands.append(self.build_op_arg(tac[1:-1], builder, gen_ctx))
        else:
            for operand in tac[1:-1]:
                if operand is not None:
                    operands.append(self.build_op_arg(operand, builder, gen_ctx))

        return operands

    def build_op_arg(self, arg, builder, gen_ctx):
        if isinstance(arg, GrammarNode):
            return arg.to_llvm(builder, gen_ctx)
        elif isinstance(arg, (tuple, list)):
            return [self.build_op_arg(x, builder, gen_ctx) for x in arg]
        else:
            return arg


class SpelInterpreter(object):
    def __init__(self):
        self.lexer = lex.lex()
        self.parser = yacc.yacc()
        # 这里的self.parser就是p.parser，可以把自己的ParseContext塞进去，记录必要的信息
        self.parser.context = ParseContext()
        self.parser.pseudo_ir_gen = PseudoIRGen(self.parser.context)
        self.llvm_runner = LLVMRunner(need_opt=False)

    def parse(self, code, run_ctx: dict):
        self.parser.context.clear()
        try:
            # 这里需指定lexer
            self.parser.context.init_sym_table(run_ctx)
            return self.parser.parse(code, lexer=self.lexer)
        except Exception as e:
            LOGGER.error(
                "parse spel:%s failed, err:%s" % (code, traceback.format_exc())
            )
            raise e

    def run(self, code, run_ctx: dict = None):
        code = code.strip()
        if not code:
            log_and_raise(SyntaxError, "code empty!")

        LOGGER.info("code:%s, run_ctx:%s" % (code, run_ctx))

        write_both_ir_log("-------------------")
        write_both_ir_log("code:%s, run_ctx:%s" % (code, run_ctx))
        ir_mod = self.parse(code, run_ctx)
        return self.make_result_pythonic(self.llvm_runner.run(ir_mod))

    def lookup_variable(self, var_name):
        addr = self.llvm_runner.get_global_value_address(var_name)
        LOGGER.info("lookup_variable var:%s addr:%d" % (var_name, addr))
        if addr == 0:
            log_and_raise(ParseError, "variable:%s NOT exists!" % var_name)

        sym_info = self.parser.context.get_sym(var_name)
        if sym_info is None:
            log_and_raise(ParseError, "undefined variable:%s" % var_name)

        var_typ = sym_info.sym_type
        if var_typ is None:
            log_and_raise(ParseError, "variable:%s has NO type!" % var_name)

        raw_res = self.llvm_runner.get_c_type(var_typ.to_llvm_type()).from_address(addr)
        LOGGER.info("lookup_variable var:%s ctype_value:%s" % (var_name, raw_res))
        return self.make_result_pythonic(self.get_ctype_value(var_typ, raw_res))

    def get_ctype_value(self, var_typ, raw_res):
        if var_typ.is_basic_type():
            return raw_res.value
        else:
            return raw_res

    def make_result_pythonic(self, result):
        # str type
        if isinstance(result, bytes):
            return result.decode("utf-8")

        # list type
        if type(result).__name__ == "DynamicListType":
            return [self.make_result_pythonic(x) for x in result.elems[: result.count]]

        # other basic type
        cont = getattr(result, "contents", None)
        if cont is None:
            return result

        return self.make_result_pythonic(cont)


class Spel4llvmTest(unittest.TestCase):
    def test_spel_run_assign_without_predef(self):
        interp = SpelInterpreter()
        self.assertEqual(interp.run("#a=true"), True)
        self.assertEqual(interp.lookup_variable("a"), True)

        self.assertEqual(interp.run("#a=FALSE"), False)
        self.assertEqual(interp.lookup_variable("a"), False)

        self.assertEqual(interp.run("#a=1"), 1)
        self.assertEqual(interp.lookup_variable("a"), 1)

        self.assertEqual(interp.run("#b=1.5"), 1.5)
        self.assertEqual(interp.lookup_variable("b"), 1.5)
        self.assertRaisesRegex(
            ParseError, "undefined variable:a", interp.lookup_variable, "a"
        )

        self.assertEqual(interp.run("#a='abc'"), "abc")
        self.assertEqual(interp.lookup_variable("a"), "abc")

        self.assertEqual(interp.run("#a={1,2,3}"), [1, 2, 3])
        self.assertEqual(interp.lookup_variable("a"), [1, 2, 3])

        self.assertEqual(interp.run("#a={{1,2},{3}}"), [[1, 2], [3]])
        self.assertEqual(interp.lookup_variable("a"), [[1, 2], [3]])

        self.assertEqual(interp.run("#a={{'a','bc'},{'de'}}"), [["a", "bc"], ["de"]])
        self.assertEqual(interp.lookup_variable("a"), [["a", "bc"], ["de"]])

        self.assertEqual(interp.run("{{'a','bc'},{'de'}}[1][0]"), "de")
        self.assertEqual(interp.run("{{'a','bc'},{'de'}}[1][0][1]"), "e")

        self.assertRaises(ParseError, interp.run, "1=2")
        self.assertEqual(interp.run("{1,2,3}[1] = 3"), 3)
        self.assertRaisesRegex(ParseError, "symbols:a undefined!", interp.run, "#a")

    def test_spel_run_assign_with_predef(self):
        interp = SpelInterpreter()
        self.assertEqual(interp.run("#a", {"a": True}), True)
        self.assertEqual(interp.lookup_variable("a"), True)

        self.assertEqual(interp.run("#a", {"a": False}), False)
        self.assertEqual(interp.lookup_variable("a"), False)

        self.assertEqual(interp.run("#a", {"a": 1}), 1)
        self.assertEqual(interp.lookup_variable("a"), 1)

        self.assertEqual(interp.run("#a", {"a": 1.5}), 1.5)
        self.assertEqual(interp.lookup_variable("a"), 1.5)

        self.assertEqual(interp.run("#a='abcd'"), "abcd")
        self.assertEqual(interp.lookup_variable("a"), "abcd")

        self.assertEqual(interp.run("#a", {"a": "efgh"}), "efgh")
        self.assertEqual(interp.lookup_variable("a"), "efgh")

        self.assertEqual(interp.run("#c", {"c": "efgh"}), "efgh")
        self.assertEqual(interp.lookup_variable("c"), "efgh")

        self.assertEqual(interp.run("#b=2", {"b": 1}), 2)
        self.assertEqual(interp.lookup_variable("b"), 2)

        self.assertEqual(interp.run("#b=2", {"b": 1.3}), 2)
        self.assertEqual(interp.lookup_variable("b"), 2)

        self.assertEqual(interp.run("#b=2", {"b": "efg"}), 2)
        self.assertEqual(interp.lookup_variable("b"), 2)

        self.assertEqual(interp.run("#a", {"a": [1, 2, 3]}), [1, 2, 3])
        self.assertEqual(interp.lookup_variable("a"), [1, 2, 3])

        self.assertEqual(interp.run("#a", {"a": ["a", "b", "c"]}), ["a", "b", "c"])
        self.assertEqual(interp.lookup_variable("a"), ["a", "b", "c"])

        self.assertEqual(
            interp.run("#a", {"a": [True, False, False]}), [True, False, False]
        )
        self.assertEqual(interp.lookup_variable("a"), [True, False, False])

        self.assertEqual(interp.run("#a", {"a": [[1, 2], [3]]}), [[1, 2], [3]])
        self.assertEqual(interp.lookup_variable("a"), [[1, 2], [3]])

        self.assertEqual(
            interp.run("#a", {"a": [["a"], ["b", "cd"]]}), [["a"], ["b", "cd"]]
        )
        self.assertEqual(interp.lookup_variable("a"), [["a"], ["b", "cd"]])

        self.assertEqual(
            interp.run("#a", {"a": [[["a"], ["b", "cd"]], [["e", "f", "g"], ["k"]]]}),
            [[["a"], ["b", "cd"]], [["e", "f", "g"], ["k"]]],
        )
        self.assertEqual(
            interp.lookup_variable("a"),
            [[["a"], ["b", "cd"]], [["e", "f", "g"], ["k"]]],
        )

        self.assertEqual(interp.run("#a[1]=4", {"a": [1, 2, 3]}), 4)
        self.assertEqual(interp.lookup_variable("a")[1], 4)
        self.assertEqual(interp.lookup_variable("a"), [1, 4, 3])

        self.assertEqual(interp.run("#a[1]='p'", {"a": ["a", "b", "c"]}), "p")
        self.assertEqual(interp.lookup_variable("a")[1], "p")
        self.assertEqual(interp.lookup_variable("a"), ["a", "p", "c"])

        self.assertEqual(interp.run("#a[1]=true", {"a": [True, False, True]}), True)
        self.assertEqual(interp.lookup_variable("a")[1], True)
        self.assertEqual(interp.lookup_variable("a"), [True, True, True])

        self.assertEqual(interp.run("#a[1]={3,4}", {"a": [[1, 2], [3]]}), [3, 4])
        self.assertEqual(interp.lookup_variable("a"), [[1, 2], [3, 4]])

        self.assertEqual(
            interp.run("#a[1]={'b', 'cd', 'ef'}", {"a": [["a"], ["b", "cd"]]}),
            ["b", "cd", "ef"],
        )
        self.assertEqual(interp.lookup_variable("a"), [["a"], ["b", "cd", "ef"]])

        self.assertEqual(
            interp.run(
                "#a[0][1]={'bc'}",
                {"a": [[["a"], ["b", "cd"]], [["e", "f", "g"], ["k"]]]},
            ),
            ["bc"],
        )
        self.assertEqual(
            interp.lookup_variable("a"),
            [[["a"], ["bc"]], [["e", "f", "g"], ["k"]]],
        )

        self.assertRaisesRegex(
            TypeError,
            "cannot assign str to int",
            lambda: interp.run("#a[1]='d'", {"a": [1, 2, 3]}),
        )

        self.assertRaisesRegex(
            TypeError,
            "cannot assign boolean to int",
            lambda: interp.run("#a[1]=false", {"a": [1, 2, 3]}),
        )

        self.assertRaisesRegex(
            TypeError,
            "cannot assign str to list<int>",
            lambda: interp.run("#a[1]='d'", {"a": [[1, 2], [3]]}),
        )
        # run_ctx = {"a": {"b": 1, "c": 2}}
        # self.assertEqual(interp.run("#a['c']=10", run_ctx), None)
        # self.assertEqual(run_ctx.get("a")["c"], 10)

    def test_spel_run_normal(self):
        interp = SpelInterpreter()
        self.assertEqual(interp.run("1+2"), 3)
        self.assertEqual(interp.run("1-2"), -1)
        self.assertEqual(interp.run("3*2"), 6)
        self.assertEqual(interp.run("5/2"), 2)
        self.assertEqual(interp.run("5/2+4"), 6)
        self.assertEqual(interp.run("5*2+4-5/2"), 12)
        self.assertEqual(interp.run("5*2+4-5%2"), 13)
        self.assertEqual(interp.run("7%3"), 1)
        self.assertEqual(interp.run("7 mod 3"), 1)
        self.assertEqual(interp.run("5.1 + 4.9"), 10.0)
        self.assertEqual(interp.run("5 + 4.9"), 9.9)
        self.assertEqual(interp.run("5/2.0+4-3.2"), 3.3)
        self.assertEqual(interp.run("5/2.0+4-3.2+2.5^2"), 9.55)
        self.assertEqual(interp.run("5/2.0+4-3.2+7/3"), 5.3)
        self.assertEqual(interp.run("10"), 10)
        self.assertEqual(interp.run("'a'"), "a")
        self.assertEqual(interp.run("7^2"), 49)
        self.assertEqual(interp.run("-10+1"), -9)
        self.assertEqual(interp.run("2+-10"), -8)
        self.assertEqual(interp.run("-10.5+1"), -9.5)
        self.assertTrue(float_eq(interp.run("2.7+-10.1"), -7.4))
        self.assertEqual(interp.run("1==2"), False)
        self.assertEqual(interp.run("2==2"), True)
        self.assertEqual(interp.run("1!=2"), True)
        self.assertEqual(interp.run("2!=2"), False)
        self.assertEqual(interp.run("1<2"), True)
        self.assertEqual(interp.run("2<2"), False)
        self.assertEqual(interp.run("3>2"), True)
        self.assertEqual(interp.run("2>2"), False)
        self.assertEqual(interp.run("3 ge 2"), True)
        self.assertEqual(interp.run("2 ge 2"), True)
        self.assertEqual(interp.run("3.6>2"), True)
        self.assertEqual(interp.run("2>2.6"), False)
        self.assertEqual(interp.run("2.26>2.6"), False)
        self.assertEqual(interp.run("'a' == 'b'"), False)
        self.assertEqual(interp.run("'a' != 'b'"), True)
        self.assertEqual(interp.run("'a' < 'b'"), True)
        self.assertEqual(interp.run("'a' <= 'b'"), True)
        self.assertEqual(interp.run("'a' > 'b'"), False)
        self.assertEqual(interp.run("'a' gt 'b'"), False)
        self.assertEqual(interp.run("'a' >= 'b'"), False)
        self.assertEqual(interp.run("'a' + 'b'"), "ab")
        self.assertEqual(interp.run("'ac' + 'b'"), "acb")
        self.assertEqual(interp.run("'a' + 'b' + 'c'"), "abc")
        self.assertEqual(interp.run("'a' + 1"), "a1")
        self.assertEqual(interp.run("'a' + 1 + 2"), "a12")
        self.assertEqual(interp.run("1 + 2 + 'a'"), "3a")
        self.assertEqual(interp.run("'a' + 1.5"), "a1.50")

        self.assertEqual(interp.run("2==2 && 3==3 and 4==4"), True)
        self.assertEqual(interp.run("2==2 && 3==3 and 4==4 and false"), False)
        self.assertEqual(interp.run("1==2 || 3>2"), True)
        self.assertEqual(interp.run("2 ne 2 or 3<=2"), False)
        self.assertEqual(interp.run("1>=2 or 1==2"), False)
        self.assertEqual(interp.run("not (2 ne 2) or 3<=2"), True)
        self.assertEqual(interp.run("not true"), False)
        self.assertEqual(interp.run("not FALSE"), True)
        self.assertEqual(interp.run("'abc'[2]"), "c")
        self.assertEqual(interp.run("'hello'[0]"), "h")
        self.assertEqual(interp.run("1==2?10: 9"), 9)
        self.assertEqual(interp.run("1!=2?10: 9"), 10)
        self.assertEqual(interp.run("11 between {11, 12}"), True)
        self.assertEqual(interp.run("11 between {10.5, 12.5}"), True)
        self.assertEqual(interp.run("10 between {11, 12}"), False)
        self.assertEqual(interp.run("10.4 between {11.1, 12}"), False)
        self.assertEqual(interp.run("1?:0"), 1)
        self.assertEqual(interp.run("null?:0"), 0)
        self.assertEqual(interp.run("#a+1", {"a": 10}), 11)
        self.assertEqual(interp.run("-#a+1", {"a": 10}), -9)
        self.assertEqual(interp.run("2+-#a", {"a": 10}), -8)
        self.assertEqual(interp.run("#a[1]", {"a": [1, 5, 6]}), 5)
        self.assertEqual(interp.run("#a[1]", {"a": "hello"}), "e")
        # self.assertEqual(interp.run("#a['e']", {'a': {'e': 4}}), 4)
        self.assertEqual(interp.run("#a+1 between {11, 12}", {"a": 10}), True)
        self.assertEqual(interp.run("#a between {11, 12}", {"a": 10}), False)
        self.assertTrue(interp.run("'hello' instanceof T(str)"))
        self.assertTrue(interp.run("1 instanceof T(int)"))
        self.assertFalse(interp.run("1 instanceof T(int) and 1 eq 3"))
        self.assertTrue(interp.run("1 instanceof T(int) or 1 eq 3"))
        self.assertFalse(interp.run("{1,2} instanceof T(str)"))
        self.assertTrue(interp.run("{1,2} instanceof T(list)"))
        self.assertTrue(
            interp.run("{1,2} instanceof T(list) or {1,2} instanceof T(str)")
        )
        # self.assertTrue(interp.run("'hello' matches '[a-z]+'"))
        # self.assertFalse(interp.run("#a matches '[a-z]+'", {'a': 'abc1'}))
        #
        # class SubStru:
        #     def __init__(self):
        #         self.e = 'hello'
        #
        # class Stru:
        #     def __init__(self):
        #         self.b = 4
        #         self.c = SubStru()
        #
        # self.assertEqual(interp.run("#a.b-2", {'a': Stru()}), 2)
        # self.assertEqual(interp.run("#a.c.e[2]", {'a': Stru()}), 'l')

    def test_spel_run_member_call(self):
        interp = SpelInterpreter()
        self.assertEqual(interp.run("#strlen('hello')+2"), 7)
        self.assertEqual(interp.run("'hello'.length()+1"), 6)
        # self.assertEqual(interp.run("'hello'[2].length()+1"), 2)
        self.assertEqual(interp.run("{1,2,3}.size()+1"), 4)
        self.assertEqual(interp.run("{'a','b'}.size()+1"), 3)
        # self.assertEqual(interp.run("'hello'.substring(1,2)"), "e")
        # self.assertEqual(interp.run("'hello'.substring(1)"), "ello")
        # self.assertEqual(interp.run("'hello'.toUpperCase()"), "HELLO")
        # self.assertEqual(interp.run("'HELLO'.toLowerCase()"), "hello")

    def test_spel_run_collection(self):
        interp = SpelInterpreter()
        self.assertEqual(interp.run("{}"), [])
        # self.assertEqual(interp.run("{}[0]"), None)
        self.assertEqual(interp.run("{1,2}"), [1, 2])
        self.assertEqual(interp.run("{1,2}[1]"), 2)
        self.assertEqual(interp.run("{1,2}[0+1]"), 2)

        self.assertEqual(interp.run("{'A','B'}"), ["A", "B"])
        self.assertEqual(
            interp.run("{{'A','B'}, {'C', 'D'}}"), [["A", "B"], ["C", "D"]]
        )
        self.assertEqual(interp.run("{{1,2},{3,4}}"), [[1, 2], [3, 4]])
        self.assertEqual(interp.run("{{1,2}, {2,3,4}}"), [[1, 2], [2, 3, 4]])
        self.assertEqual(interp.run("{{1,2}, {2,3,4}}[1]"), [2, 3, 4])
        self.assertEqual(interp.run("{{'A','B'}, {'C', 'D'}}[1]"), ["C", "D"])
        self.assertEqual(interp.run("{{'A','B'}, {'C', 'D'}}[1][0]"), "C")
        self.assertEqual(interp.run("{{'A','B'}, {'Cme', 'D'}}[1][0][2]"), "e")
        self.assertEqual(interp.run("{'A','B'}[1]"), "B")
        self.assertEqual(interp.run("{{1,2},{3,4}}[1]"), [3, 4])
        self.assertEqual(interp.run("{{1,2},{3,4}}"), [[1, 2], [3, 4]])
        self.assertEqual(interp.run("{{1,2}, {2,3,4}}"), [[1, 2], [2, 3, 4]])
        self.assertEqual(
            interp.run("{{{1,2},{3,4}}, {{5,6},{7,8}}}"),
            [[[1, 2], [3, 4]], [[5, 6], [7, 8]]],
        )
        self.assertEqual(
            interp.run("{{{1,2},{3,4}}, {{5,6},{7,8}}}[1]"),
            [[5, 6], [7, 8]],
        )
        self.assertEqual(
            interp.run("{{{1,2},{3,4}}, {{5,6},{7,8}}}[1][0]"),
            [5, 6],
        )
        self.assertEqual(
            interp.run("{{{1,2},{3,4}}, {{5,6},{7,8}}}[1][0][1]"),
            6,
        )
        self.assertEqual(interp.run("{{1,2},{3,4,5}}"), [[1, 2], [3, 4, 5]])

        self.assertRaisesRegex(
            TypeError,
            "list element #1 is not of type int",
            lambda: interp.run("{1, 'a', 4}[1]"),
        )
        # self.assertEqual(interp.run("{1,2}[2]"), None)
        # self.assertEqual(interp.run("{1:'a',2:'b'}"), {1: "a", 2: "b"})
        # self.assertEqual(interp.run("{1:'a',2:'b'}[2]"), "b")

        # self.assertEqual(interp.run("{'a':{1,2,3}, 'b':{1,2}}['a'][2]"), 3)
        # self.assertEqual(interp.run("{1,2}.![#this+1]"), [2, 3])
        # self.assertEqual(interp.run("{1,2,3,4}.?[#this%2==0]"), [2, 4])
        # self.assertEqual(interp.run("{1,2}?.![#this+1]"), [2, 3])
        # self.assertEqual(interp.run("{1,2,3,4}?.?[#this%2==0]"), [2, 4])
        #
        # class Stru:
        #     def __init__(self, value):
        #         self.value = value
        #
        #     def __eq__(self, other):
        #         if isinstance(other, Stru):
        #             return self.value == other.value
        #         else:
        #             return False
        #
        # l = [Stru(1), Stru(2), Stru(3), Stru(4)]
        # self.assertEqual(interp.run("#l.![#this.value+1]", {"l": l}), [2, 3, 4, 5])
        # self.assertEqual(
        #     interp.run("#l?.?[#this.value%2==0]", {"l": l}), [Stru(2), Stru(4)]
        # )
        #
        # l = [Stru(1), Stru(2), Stru(3), Stru(4), Stru(5), Stru(6)]
        # self.assertEqual(
        #     interp.run("#l?.?[#this.value%2==0].![#this.value]", {"l": l}), [2, 4, 6]
        # )
        # self.assertEqual(
        #     interp.run(
        #         "#l?.?[#this.value%2==0 && #this.value%3==1].![#this.value]", {"l": l}
        #     ),
        #     [4],
        # )
        # self.assertEqual(
        #     interp.run(
        #         "#l?.?[#this.value%2==0 && #this.value%3==1 or #this.value%3==0].![#this.value]",
        #         {"l": l},
        #     ),
        #     [3, 4, 6],
        # )
        #
        # self.assertEqual(interp.run("{1:'a',2:'b'}.![#this.key]"), [1, 2])
        # self.assertEqual(
        #     interp.run("{1:'a',2:'b'}.?[#this.key%2==0].![#this.value]"), ["b"]
        # )
        # self.assertEqual(interp.run("{1:'a',2:'b'}.![#this.value]"), ["a", "b"])
        # self.assertEqual(
        #     interp.run("{1:'a',2:'b'}.?[#this.value == 'a'].![#this.key]"), [1]
        # )
        #
        # self.assertEqual(interp.run("{1:'a',2:'b'}.![key]"), [1, 2])
        # self.assertEqual(interp.run("{1:'a',2:'b'}.?[key%2==0].![value]"), ["b"])
        # self.assertEqual(interp.run("{1:'a',2:'b'}.![value]"), ["a", "b"])
        # self.assertEqual(interp.run("{1:'a',2:'b'}.?[value == 'a'].![key]"), [1])
        # self.assertEqual(
        #     interp.run("#m.?[value == 'a'].![key]", {"m": {1: "a", 2: "b"}}), [1]
        # )

    @unittest.skip
    def test_spel_run_func(self):
        interp = SpelInterpreter()

        def f(n):
            return n**2

        self.assertEqual(interp.run("#f(3)", {"f": f}), 9)

        class Stru:
            def __init__(self, offset=0):
                self.offset = offset

            def f(self, n):
                return n * 2 + self.offset

        self.assertEqual(interp.run("#a.f(3)", {"a": Stru()}), 6)

        def g(n):
            return Stru(n)

        self.assertEqual(interp.run("#g(3).f(5)", {"g": g}), 13)

        class Stru1:
            def f(self, n):
                return Stru(n)

        self.assertEqual(interp.run("#a.f(3).f(10)", {"a": Stru1()}), 23)

    @unittest.skip
    def test_spel_run_inc_dec(self):
        interp = SpelInterpreter()

        def f(m, n):
            return m + n

        run_ctx = {"f": f, "a": 1, "b": 2}
        self.assertEqual(interp.run("#f(++#a, ++#b)", run_ctx), 5)
        self.assertEqual(run_ctx.get("a"), 2)
        self.assertEqual(run_ctx.get("b"), 3)

        run_ctx = {"f": f, "a": 1, "b": 2}
        self.assertEqual(interp.run("#f(#a++, #b++)", run_ctx), 3)
        self.assertEqual(run_ctx.get("a"), 2)
        self.assertEqual(run_ctx.get("b"), 3)

        run_ctx = {"f": f, "a": 1, "b": 2}
        self.assertEqual(interp.run("#f(--#a, --#b)", run_ctx), 1)
        self.assertEqual(run_ctx.get("a"), 0)
        self.assertEqual(run_ctx.get("b"), 1)

        run_ctx = {"f": f, "a": 1, "b": 2}
        self.assertEqual(interp.run("#f(#a--, #b--)", run_ctx), 3)
        self.assertEqual(run_ctx.get("a"), 0)
        self.assertEqual(run_ctx.get("b"), 1)

        run_ctx = {"b": 10}
        self.assertEqual(interp.run("#a=++#b", run_ctx), None)
        self.assertEqual(run_ctx.get("a"), 11)
        self.assertEqual(run_ctx.get("b"), 11)

        run_ctx = {"b": 10}
        self.assertEqual(interp.run("#a=#b++", run_ctx), None)
        self.assertEqual(run_ctx.get("a"), 10)
        self.assertEqual(run_ctx.get("b"), 11)

    @unittest.skip
    def test_ast_run_safe_navi(self):
        interp = SpelInterpreter()

        class SubStru:
            def __init__(self):
                self.e = "hello"

        class Stru:
            def __init__(self, sub_stru=None):
                self.b = 4
                self.c = sub_stru

        self.assertEqual(interp.run("#a?.b", {"a": Stru()}), 4)
        self.assertEqual(interp.run("#a?.b", {"a": None}), None)
        self.assertEqual(interp.run("#a.b", {"a": Stru()}), 4)
        self.assertEqual(interp.run("#a?.c?.e", {"a": Stru()}), None)
        self.assertEqual(interp.run("#a?.c?.e", {"a": Stru(SubStru())}), "hello")

        run_ctx = {"l": []}
        self.assertEqual(interp.run("#l?.append('e')", run_ctx), None)
        self.assertTrue("e" in run_ctx.get("l"))

        self.assertEqual(interp.run("#l?.append('e')", {"l": None}), None)

    def test_spel_run_error(self):
        interp = SpelInterpreter()
        self.assertRaises(TypeError, interp.run, "!1")
        self.assertRaises(SyntaxError, interp.run, "")
        self.assertRaisesRegex(
            TypeError, "list element #1 is not of type int", interp.run, "{1,2.8}"
        )
        self.assertRaisesRegex(
            TypeError, "str type cannot support op:-", interp.run, "'a' - 'b'"
        )
        self.assertRaisesRegex(
            TypeError,
            "arg1/arg2 type should be same when tri operation",
            interp.run,
            "1==0?9.5:10",
        )
        # self.assertRaises(NameError, interp.run, "a=1")
        # self.assertRaises(AttributeError, interp.run, "{1:'a',2:'b'}.![key1]")


if __name__ == "__main__":
    unittest.main()
